package com.un.ebs.customer.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.un.ebs.core.UserContext;
import com.un.ebs.core.enums.BooleanEnum;
import com.un.ebs.core.enums.SexEnum;
import com.un.ebs.core.exception.BusinessEntityNotFoundException;
import com.un.ebs.core.exception.BusinessException;
import com.un.ebs.core.exception.BusinessPermissionDeniedException;
import com.un.ebs.core.util.DateUtils;
import com.un.ebs.core.util.SecurityUtils;
import com.un.ebs.core.util.StringUtils;
import com.un.ebs.customer.CustomerConfigureCode;
import com.un.ebs.customer.domain.*;
import com.un.ebs.customer.dto.CustomerBookDto;
import com.un.ebs.customer.dto.CustomerBookRequest;
import com.un.ebs.customer.dto.CustomerTraceAddDto;
import com.un.ebs.customer.mapper.CustomerTraceBookExtMapper;
import com.un.ebs.customer.mapper.CustomerTraceBookMapper;
import com.un.ebs.customer.mapper.CustomerTraceExtMapper;
import com.un.ebs.customer.mapper.CustomerTraceMapper;
import com.un.ebs.customer.service.CustomerService;
import com.un.ebs.customer.service.CustomerTraceService;
import com.un.ebs.customer.service.DefunctService;
import com.un.ebs.order.domain.OrderExt;
import com.un.ebs.order.dto.OrderAddDto;
import com.un.ebs.order.dto.OrderItemDto;
import com.un.ebs.order.enums.DeliverTypeEnum;
import com.un.ebs.order.service.OrderService;
import com.un.ebs.product.domain.ProductExt;
import com.un.ebs.product.domain.ProductSkuExt;
import com.un.ebs.product.dto.ProductCanBookSpuTimeResponse;
import com.un.ebs.product.dto.ProductClassTimeVo;
import com.un.ebs.product.service.ProductClassTimeService;
import com.un.ebs.product.service.ProductService;
import com.un.ebs.product.service.ProductSkuService;
import com.un.ebs.sys.domain.Personnel;
import com.un.ebs.sys.service.DictionaryValueService;
import com.un.ebs.sys.service.PersonnelService;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author liubo
 * @since 2019-09-24
 */
@Service
public class CustomerTraceServiceImpl extends ServiceImpl<CustomerTraceMapper, CustomerTrace> implements CustomerTraceService {

    @Resource
    private CustomerTraceExtMapper customerTraceExtMapper;

    @Resource
    private DictionaryValueService dictionaryValueService;

    @Resource
    private PersonnelService personnelService;

    @Resource
    private DefunctService defunctService;

    @Resource
    private CustomerService customerService;

    @Resource
    private ProductService productService;

    @Resource
    private ProductSkuService productSkuService;

    @Resource
    private ProductClassTimeService productClassTimeService;

    @Resource
    private CustomerTraceBookMapper customerTraceBookMapper;

    @Resource
    private CustomerTraceBookExtMapper customerTraceBookExtMapper;

    @Resource
    private OrderService orderService;

    @Override
    public String add(CustomerTraceAddDto req) {

        dictionaryValueService.hasValue(CustomerConfigureCode.customer_trace_dict, req.getTraceDict(), null, true);

        Personnel p = personnelService.queryById(req.getPersonnelId());
        CustomerTrace obj = new CustomerTrace();
        obj.setCustomerId(req.getCustomerId());
        obj.setTraceDict(req.getTraceDict());
        obj.setPersonnelId(req.getPersonnelId());
        if (p != null) {
            obj.setPersonnelName(p.getName());
        }

        if (req.getPlanDate() == null) {
            obj.setPlanDate(new Date());
            obj.setRealDate(new Date());
        } else {
            obj.setPlanDate(req.getPlanDate());
        }
        obj.setRemark(req.getRemark());
        obj.setRemindRemark(req.getRemindRemark());
        obj.setActivityCustomerId(req.getActivityCustomerId());
        obj.insert();
        return obj.getId();
    }

    @Deprecated
    @Transactional
    @Override
    public String addBook(String custId, String dictType, Date time, String bookDesc) {
        CustomerBookRequest req = new CustomerBookRequest();
        req.setCustId(custId);
        req.setDictType(dictType);
        req.setTime(time);
        req.setBookDesc(bookDesc);
        return addBook(req);
    }

    @Transactional
    @Override
    public String addBook(CustomerBookRequest req) {

        if (DateUtils.parseDate(req.getTime(), "yyyy-MM-dd").equals(DateUtils.parseDate(new Date(), "yyyy-MM-dd"))) {
            throw new BusinessException("不能预约当天的。");
        }
        if (req.getTime().before(new Date())) {
            throw new BusinessException("预约时间必须在当前时间之后。");
        }
        if (StringUtils.isNullOrEmpty(req.getDictType())) {
            req.setDictType(CustomerConfigureCode.customer_trace_dict_book_default);
        }
        if (!dictionaryValueService.hasValue(CustomerConfigureCode.customer_trace_dict, req.getDictType(), null, false)) {
            throw new BusinessException("预约类别 " + req.getDictType() + " 无效。");
        }

        Customer cust = customerService.queryById(req.getCustId());
        if (cust == null) {
            List<String> ids = customerService.queryCustIdByUserId(UserContext.getInstance().getId());
            if (ids.size() > 1) {
                LoggerFactory.getLogger(this.getClass()).error(UserContext.getInstance().getUserName() + " 账号下存在多笔客户，需要合并。");
                throw new BusinessException(UserContext.getInstance().getUserName() + " 账号下存在多笔客户，需要合并。");
            }
            cust = customerService.queryById(ids.get(0));
            if (cust == null) {
                throw new BusinessException("您需要先进行客户登记，才能进行预约。");
            }
            if (StringUtils.isNullOrEmpty(cust.getPhone())) {
                throw new BusinessException("客户信息没有实名认证，不能进行预约。");
            }
        }

        if (!StringUtils.isNullOrEmpty(req.getSpuId())) {
            Optional<ProductCanBookSpuTimeResponse> book = queryCanBookSpu(req.getSpuId(), req.getTime()).stream().filter(s -> req.getTimeId().equals(s.getTimeId())).findFirst();
            if (book.isPresent()) {
                if (book.get().getMaxBookCount() - book.get().getCurrBookCount() <= 0) {
                    LoggerFactory.getLogger(this.getClass()).warn(String.format("预约已满，请预约其它时间： spuId : %s , maxCount : %s , currCount : %s , day : %s", req.getSpuId(), book.get().getMaxBookCount(), book.get().getCurrBookCount(), DateUtils.formatDate(req.getTime(), "yyyy-MM-dd")));
                    throw new BusinessException("预约已满，请预约其它时间。");
                }
            }

            ProductExt spu = productService.queryViewById(req.getSpuId());
            if (spu != null) {
                if (spu.getIsMustDefunctByOrder()) {
                    if (StringUtils.isNullOrEmpty(req.getDefunctId()) && StringUtils.isNullOrEmpty(req.getDefunctName())) {
                        throw new BusinessException("墓位使用者没有指定。");
                    }
                }
            }
        }

        if (!StringUtils.isNullOrEmpty(req.getDefunctId()) && !StringUtils.isNullOrEmpty(req.getCustId())) {
            defunctService.bind(req.getCustId(), req.getDefunctId());
        }

        CustomerTrace obj = new CustomerTrace();
        obj.setCustomerId(req.getCustId());
        obj.setTraceDict(req.getDictType());
        obj.setPlanDate(req.getTime());
        obj.setRemark(req.getBookDesc());
        obj.setTagId(req.getDefunctId());
        if (!StringUtils.isNullOrEmpty(req.getStartTime())) {
            obj.setPlanDate(DateUtils.parseDate(DateUtils.formatDate(req.getTime(), "yyyy-MM-dd ") + req.getStartTime(), "yyyy-MM-dd HH:mm"));
        }
        if (!StringUtils.isNullOrEmpty(req.getEndTime())) {
            obj.setPlanCompleteTime(DateUtils.parseDate(DateUtils.formatDate(req.getTime(), "yyyy-MM-dd ") + req.getEndTime(), "yyyy-MM-dd HH:mm"));
        }

        if (!StringUtils.isNullOrEmpty(cust.getPersonnelId())) {
            obj.setPersonnelId(cust.getPersonnelId());
            obj.setPersonnelName(personnelService.queryById(cust.getPersonnelId()).getName());
        }
        obj.insert();

        if (!StringUtils.isNullOrEmpty(req.getSpuId()) && !StringUtils.isNullOrEmpty(req.getTimeId())) {
            CustomerTraceBook objBook = new CustomerTraceBook();
            objBook.setTraceId(obj.getId());
            objBook.setBookTimeId(req.getTimeId());
            objBook.setBookDay(DateUtils.parseDate(req.getTime(), "yyyy-MM-dd"));
            objBook.setBookSpuId(req.getSpuId());
            objBook.setRemark(req.getBookDesc());
            objBook.setStartTime(req.getStartTime());
            objBook.setEndTime(req.getEndTime());
            objBook.setDefunctId(req.getDefunctId());
            objBook.setDefunctName(req.getDefunctName());
            objBook.setTombSkuId(req.getTombSkuId());
            objBook.insert();
        }
        return obj.getId();
    }

    @Transactional
    @Override
    public String createOrderByBook(String bookId, String skuId, String tombSkuId, Integer qty) {
        // 原始表中查询数据，主要是逝者信息是否在预约时提供
        CustomerTraceBook obj = customerTraceBookMapper.selectById(bookId);
        if (obj == null) {
            throw new BusinessException("没有找到指定的预约记录。");
        }

        // 逝者重名逻辑处理
        DefunctExt defunctExt;
        if (StringUtils.isNullOrEmpty(obj.getDefunctId()) && !StringUtils.isNullOrEmpty(obj.getDefunctName())) {
            // 如果预约时只提供了逝者名称
            String id = defunctService.nameExist(obj.getDefunctName(), baseMapper.selectById(obj.getTraceId()).getCustomerId());
            if (StringUtils.isNullOrEmpty(id)) {
                id = defunctService.add(obj.getDefunctName(), tombSkuId);
            }
            defunctExt = defunctService.queryViewById(id);
        } else {
            // 如果预约时已指定了逝者ID
            defunctExt = defunctService.queryViewById(obj.getDefunctId());
        }
        if (defunctExt == null) {
            throw new BusinessException("墓位使用者没有找到。");
        }

        // 重新执行查询，确认预约是否存在有效订单
        CustomerTraceBookExt bookExt = customerTraceBookExtMapper.selectById(bookId);
        if (bookExt.getOrderItemCount() > 0) {
            throw new BusinessException("此预约已存在有效订单，不能继续创建订单。");
        }

        OrderAddDto order = new OrderAddDto();
        order.setTraceId(obj.getTraceId());
        order.setCustId(bookExt.getCustomerId());
        order.setDefunctId(defunctExt.getId());
        order.setDefunctTombSkuId(tombSkuId);
        order.setLinkman(bookExt.getCustomerName());
        order.setLinkmanPhone(bookExt.getCustomerPhone());
        order.setPersonnelId(SecurityUtils.getUserContent().getPersonnelId());
        order.setRemark("根据预约创建的订单。");
        order.setItems(new ArrayList<>());

        ProductSkuExt sku = productSkuService.queryViewById(skuId);
        if (sku == null) {
            throw new BusinessException("指定的SKU无效。");
        }

        OrderItemDto item = new OrderItemDto();
        item.setSkuId(skuId);
        item.setPrice(sku.getPrice());
        item.setQty(qty);
        item.setDeliverType(DeliverTypeEnum.Default);
        item.setStartTime(DateUtils.parseDate(obj.getBookDay(), obj.getStartTime().split(":")[0], obj.getStartTime().split(":")[1]));
        item.setEndTime(DateUtils.parseDate(obj.getBookDay(), obj.getEndTime().split(":")[0], obj.getEndTime().split(":")[1]));
        item.setRemark(obj.getRemark());
        order.getItems().add(item);

        String id = orderService.add(order);
        orderService.submit(id);
        return id;
    }

    @Override
    public void updateBookTime(String traceId, Date time, String bookDesc) {
        CustomerTrace obj = queryById(traceId);
        if (obj == null) {
            throw new BusinessEntityNotFoundException(traceId, "预约");
        }

        Customer cust = customerService.queryById(obj.getCustomerId());

        if (!(SecurityUtils.getUserContent().getId().equals(obj.getCreateAt()) ||
                SecurityUtils.getUserContent().getId().equals(cust.getCreateAt()) ||
                SecurityUtils.getUserContent().getPersonnelId().equals(cust.getPersonnelId())
        )) {
            throw new BusinessPermissionDeniedException("你不是客户本人，也不是客户的服务顾问，不能进行此操作。");
        }

        if (obj.getPlanCompleteTime() != null) {
            throw new BusinessException("该预约在 " + DateUtils.formatDate(obj.getPlanCompleteTime()) + " 关闭");
        }

        if (time.before(new Date())) {
            throw new BusinessException("预约时间必须在当前时间之后。");
        }

        obj.setPlanDate(time);
        obj.setRemark(bookDesc);
        obj.updateById();
    }

    @Override
    public void reply(String traceId, String remark) {
        CustomerTrace trace = baseMapper.selectById(traceId);
        if (trace == null) {
            throw new BusinessEntityNotFoundException(traceId, "客户服务记录");
        }

        CustomerTrace obj = new CustomerTrace();
        obj.setCustomerId(trace.getCustomerId());
        obj.setTraceDict(CustomerConfigureCode.customer_service_reply);
        obj.setPlanDate(new Date());
        obj.setRealDate(new Date());
        obj.setRemark(remark);
        obj.setParentId(trace.getId());
        obj.setPersonnelId(SecurityUtils.getUserContent().getPersonnelId());
        obj.setPersonnelName(SecurityUtils.getUserContent().getPersonnelName());
        obj.setIsPlan(false);
        obj.insert();
    }

    @Override
    public void visit(String traceId, String remark) {
        if(StringUtils.isNullOrEmpty(remark)){
            throw new BusinessException("请填写回访内容。");
        }
        CustomerTrace obj = baseMapper.selectById(traceId);
        if (obj == null) {
            throw new BusinessEntityNotFoundException(traceId, "客户服务记录");
        }
        obj.setRealDate(new Date());
        obj.setRemark(remark);
        obj.setPersonnelId(SecurityUtils.getUserContent().getPersonnelId());
        obj.setPersonnelName(SecurityUtils.getUserContent().getPersonnelName());
        obj.setIsPlan(false);
        obj.updateById();
    }

    @Override
    public void completeBook(String traceId) {
        CustomerTrace obj = baseMapper.selectById(traceId);
        if (obj == null) {
            throw new BusinessEntityNotFoundException(traceId, "预约");
        }

        if (StringUtils.isNullOrEmpty(obj.getPersonnelId())) {
            obj.setPersonnelId(SecurityUtils.getUserContent().getPersonnelId());
            obj.setPersonnelName(SecurityUtils.getUserContent().getPersonnelName());
        } else {
            if (!SecurityUtils.getUserContent().getPersonnelId().equals(obj.getPersonnelId())) {
                throw new BusinessException("此当预约只能由 【" + obj.getPersonnelName() + "】 处理。");
            }
        }

        obj.setPlanCompleteTime(new Date());
        obj.setRealDate(new Date());
        obj.setId(traceId);
        obj.updateById();
    }

    @Transactional
    @Override
    public void cancelBook(String traceId, String remark) {
        CustomerTrace obj = baseMapper.selectById(traceId);
        if (obj == null) {
            throw new BusinessEntityNotFoundException(traceId, "预约");
        }

        obj.setRealDate(new Date());
        obj.setPlanCompleteTime(obj.getRealDate());
        obj.setRemark(StringUtils.isNullOrEmpty(remark) ? "预约取消" : remark);
        obj.updateById();

        // 订单撤销
        List<OrderExt> orders = orderService.queryByTrace(traceId);
        if (orders.stream().filter(s -> s.getAcceptStatus() == true).count() > 0) {
            throw new BusinessException("您的预约已受理，不能撤销。");
        }
        for (OrderExt ord : orders) {
            orderService.submitCancel(ord.getId(), "预约取消");
        }

        // 预约撤销
        LambdaQueryWrapper<CustomerTraceBook> lq = Wrappers.lambdaQuery();
        lq.eq(CustomerTraceBook::getTraceId, traceId);
        customerTraceBookMapper.delete(lq);
    }

    @Override
    public List<CustomerBookDto> queryBook(String custId) {
        List<String> ids = new ArrayList<>();
        if (StringUtils.isNullOrEmpty(custId)) {
            ids = customerService.queryCustIdByUserId(SecurityUtils.getUserContent().getId());
        } else {
            ids.add(custId);
        }
        if (StringUtils.isNullOrEmpty(ids)) {
            LoggerFactory.getLogger(this.getClass()).warn(String.format("当前用户没有绑定客户。 userId : %s", SecurityUtils.getUserContent().getId()));
            throw new BusinessException("你的账号没有作为客户身份进行实名登记。");
        }

        LambdaQueryWrapper<CustomerTraceBookExt> lq = Wrappers.lambdaQuery();
        lq.in(CustomerTraceBookExt::getCustomerId, ids);
        return customerTraceBookExtMapper.selectList(lq).stream().map(s -> {
            CustomerBookDto obj = new CustomerBookDto();
            obj.setCustId(s.getCustomerId());
            obj.setCustName(s.getCustomerName());
            obj.setCustPhone(s.getCustomerPhone());
            obj.setTraceId(s.getTraceId());
            obj.setId(s.getId());
            obj.setBookTitle(s.getSpuNameAs());
            obj.setBookTime(DateUtils.parseDate(s.getBookDay(), "yyyy-MM-dd"));
            obj.setCreateTime(s.getCreateTime());
            obj.setStartTime(s.getStartTime());
            obj.setEndTime(s.getEndTime());
            obj.setRemark(s.getRemark());
            obj.setDefunctId(s.getDefunctId());
            obj.setDefunctName(s.getDefunctName());
            obj.setDefunctSex(SexEnum.valueOf(s.getDefunctSex()));
            obj.setTombSkuId(s.getTombSkuId());
            obj.setTombSkuNameAs(s.getTombSkuNameAs());
            obj.setOrderItemCount(s.getOrderItemCount());
            if (!StringUtils.isNullOrEmpty(s.getTombSkuNameAs())) {
                obj.setTombDefuncts(productSkuService.querySaleByTomb(s.getTombSkuNameAs(), null, 100, 1).getRecords());
            }

            return obj;
        }).collect(Collectors.toList());
    }

    @Override
    public CustomerTraceBookExt queryBookByTrace(String traceId) {
        return customerTraceBookExtMapper.selectById(traceId);
    }

    @Override
    public IPage<CustomerTraceBookExt> queryAllBook(String searchText, int pageSize, int pageIndex) {
        LambdaQueryWrapper<CustomerTraceBookExt> lq = Wrappers.lambdaQuery();
        lq.eq(CustomerTraceBookExt::getOrderItemCount, 0);
        if (!StringUtils.isNullOrEmpty(searchText)) {
            lq.like(CustomerTraceBookExt::getCustomerName, searchText).or()
                    .like(CustomerTraceBookExt::getDefunctName, searchText).or()
                    .like(CustomerTraceBookExt::getCustomerPhone, searchText).or()
                    .like(CustomerTraceBookExt::getSpuNameAs, searchText);
        }
        Page<CustomerTraceBookExt> page = new Page<>(pageIndex, pageSize);
        lq.orderByAsc(CustomerTraceBookExt::getBookDay);
        return customerTraceBookExtMapper.selectPage(page, lq);
    }

    @Override
    public List<ProductCanBookSpuTimeResponse> queryCanBookSpu(String spuId, Date day) {
        ProductExt spu = productService.queryById(spuId);
        if (spu == null) {
            return null;
        }
        if (!spu.getIsBook()) {
            throw new BusinessException(String.format("%s 不支持预约。", spu.getNameAs()));
        }

        List<ProductCanBookSpuTimeResponse> ret = new ArrayList<>();

        LambdaQueryWrapper<CustomerTraceBook> lq = Wrappers.lambdaQuery();
        lq.eq(CustomerTraceBook::getBookSpuId, spuId);
        lq.eq(CustomerTraceBook::getBookDay, day);
        lq.orderByAsc(CustomerTraceBook::getStartTime);
        List<CustomerTraceBook> books = customerTraceBookMapper.selectList(lq);
        List<ProductClassTimeVo> times = productClassTimeService.query(spu.getClassId());
        for (ProductClassTimeVo t : times) {
            ProductCanBookSpuTimeResponse obj = new ProductCanBookSpuTimeResponse();
            obj.setTimeId(t.getId());
            obj.setStartTime(t.getStartValue());
            obj.setEndTime(t.getEndValue());
            obj.setMaxBookCount(spu.getPreTimeBookQty());
            obj.setCurrBookCount(books.stream()
                    .filter(s -> t.getId().equals(s.getBookTimeId()))
                    .filter(s -> s.getBookDay().equals(day))
                    .filter(s -> spu.getId().equals(s.getBookSpuId()))
                    .count());
            ret.add(obj);
        }
        return ret;
    }

    @Override
    public CustomerTrace queryById(String id) {
        return getBaseMapper().selectById(id);
    }

    @Override
    public CustomerTraceExt queryViewById(String id) {
        return customerTraceExtMapper.selectById(id);
    }

    @Override
    public List<CustomerTraceExt> queryByCustId(String id) {
        List<String> ids = new ArrayList<>();
        ids.add(id);
        return queryByCustId(ids);
    }

    @Override
    public List<CustomerTraceExt> queryByCustId(List<String> ids) {
        if (ids.size() <= 0) {
            return null;
        }
        LambdaQueryWrapper<CustomerTraceExt> lq = Wrappers.lambdaQuery();
        lq.in(CustomerTraceExt::getCustomerId, ids);
        lq.orderByDesc(CustomerTraceExt::getSortTime);
        List<CustomerTraceExt> result = customerTraceExtMapper.selectList(lq);
        for (int i = 0; i < result.size(); i++) {
            result.get(i).setTraceDictDesc(dictionaryValueService.queryValue(CustomerConfigureCode.customer_trace_dict, result.get(i).getTraceDict()));
        }
        return result;
    }

    @Override
    public IPage<CustomerTraceExt> queryByTraceType(String customerId, String traceDict, BooleanEnum isPlan, int pageSize, int pageIndex) {
        Page<CustomerTraceExt> page = new Page<>(pageIndex, pageSize);
        LambdaQueryWrapper<CustomerTraceExt> lq = Wrappers.lambdaQuery();
        lq.eq(CustomerTraceExt::getTraceDict, traceDict);
        if (!StringUtils.isNullOrEmpty(customerId)) {
            lq.eq(CustomerTraceExt::getCustomerId, customerId);
        }
        switch (isPlan) {
            case Yes:
                lq.eq(CustomerTraceExt::getIsPlan, true);
                break;
            case No:
                lq.eq(CustomerTraceExt::getIsPlan, false);
                break;
            default:
                break;
        }
        lq.orderByAsc(CustomerTraceExt::getPlanDate);

        return customerTraceExtMapper.selectPage(page, lq);
    }

}